package com.lujianfei.switchtheme.utils

import android.content.Context
import android.content.pm.PackageManager
import android.content.res.AssetManager
import android.content.res.ColorStateList
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.TextUtils
import android.util.Log
import com.lujianfei.library_switch_theme.callback.SkinUpdateListener
import com.lujianfei.switchtheme.MyApplication
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File

class SkinManager private constructor() {
    companion object {
        const val TAG = "SkinManager"
        var INSTANCE = SkinManager()
    }

    private var mSkinResources: Resources? = null
    private var context: Context? = null
    private var skinPackageName: String? = null
    private var isExternalSkin = false
    private val mListeners = arrayListOf<SkinUpdateListener>()
    private fun judge() {
        checkNotNull(context) { "context is null" }
    }

    init {
        Log.d("Application==", "SkinManager")
    }

    fun initManager(context: Context) {
        Log.d("Application==", "initManager")
        this.context = context.applicationContext
        isExternalSkin = !TextUtils.isEmpty(SPRepository.INSTANCE.skin_path)
        loadSkin(getSkinPath())
    }

    /**
     * API
     */
    fun addSkinUpdateListener(listener: SkinUpdateListener?) {
        if (listener == null) return
        judge()
        synchronized(mListeners) {
            mListeners.add(listener)
        }
    }


    fun removeSkinUpdateListener(listener: SkinUpdateListener?) {
        if (listener == null) return
        judge()
        synchronized(mListeners) {
            val iterator = mListeners.iterator()
            while (iterator.hasNext()) {
                if (iterator.next() == listener) {
                    iterator.remove()
                }
            }
        }
    }

    private fun getSkinPath(): String? {
        judge()
        val skinPath = SPRepository.INSTANCE.skin_path
        return if (TextUtils.isEmpty(skinPath)) null else skinPath
    }

    private fun saveSkinPath(path: String?) {
        judge()
        path?.let {
            SPRepository.INSTANCE.skin_path = path
        }
    }

    fun loadSkin(path: String?) {
        judge()
        if (path == null) return
        CoroutineScope(Dispatchers.Main).launch {
            mSkinResources = withContext(Dispatchers.IO) {
                loadResouce(path)
            }
            mSkinResources?.let {
                isExternalSkin = true
                notifySkinUpdate()
            }
        }
    }

    private fun loadResouce(path: String?): Resources? {
        if (path == null) return null
        val file = File(path)
        if (!file.exists()) return null
        val mPm = context?.packageManager
        mPm?.let {
            skinPackageName =
                    mPm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES)?.packageName
            val assetManager = AssetManager::class.java.newInstance()
            val addAssetPath = assetManager.javaClass.getMethod(
                    "addAssetPath",
                    String::class.java
            )
            addAssetPath.invoke(assetManager, path)
            val superRes = context!!.resources
            val skinResource = Resources(
                    assetManager, superRes
                    .displayMetrics, superRes.configuration
            )
            saveSkinPath(path)
            return skinResource
        }
        return null
    }

    fun getColor(resId: Int): Int? {
        judge()
        val originColor = context?.resources?.getColor(resId)
        val resName = context?.resources?.getResourceEntryName(resId)
        if (mSkinResources == null || !isExternalSkin) {
            return originColor
        }
        val newResId = mSkinResources?.getIdentifier(resName, "color", skinPackageName)
        newResId?.let {
            return try {
                mSkinResources?.getColor(newResId)
            } catch (e: NotFoundException) {
                Log.e(TAG, "$e")
                originColor
            }
        }
        return originColor
    }

    fun getColorStateList(resId: Int): ColorStateList? {
        judge()
        val originColor = context?.resources?.getColorStateList(resId)
        val resName = context?.resources?.getResourceEntryName(resId)
        if (mSkinResources == null || !isExternalSkin) {
            return originColor
        }
        val newResId = mSkinResources?.getIdentifier(resName, "color", skinPackageName)
        newResId?.let {
            return try {
                mSkinResources?.getColorStateList(newResId)
            } catch (e: NotFoundException) {
                Log.e(TAG, "$e")
                originColor
            }
        }
        return originColor
    }

    fun getDimension(resId: Int): Float? {
        judge()
        val originColor = context?.resources?.getDimension(resId)
        val resName = context?.resources?.getResourceEntryName(resId)
        if (mSkinResources == null || !isExternalSkin) {
            return originColor
        }
        val newResId = mSkinResources?.getIdentifier(resName, "dimen", skinPackageName)
        newResId?.let {
            return try {
                mSkinResources?.getDimension(newResId)
            } catch (e: NotFoundException) {
                Log.e(TAG, "$e")
                originColor
            }
        }
        return originColor
    }

    fun getDrawable(resId: Int): Drawable? {
        judge()
        val originColor = context?.resources?.getDrawable(resId)
        val resName = context?.resources?.getResourceEntryName(resId)
        if (mSkinResources == null || !isExternalSkin) {
            return originColor
        }
        val newResId = mSkinResources?.getIdentifier(resName, "drawable", skinPackageName)
        newResId?.let {
            return try {
                mSkinResources?.getDrawable(newResId)
            } catch (e: NotFoundException) {
                Log.e(TAG, "$e")
                originColor
            }
        }
        return originColor
    }

    private fun getPackage(): String? {
        if (mSkinResources == null || !isExternalSkin) {
            return MyApplication.INSTANCE.packageName
        }
        return skinPackageName
    }

    fun getResource(): Resources? {
        judge()
        if (mSkinResources == null || !isExternalSkin) {
            return context?.resources
        }
        return mSkinResources
    }

    fun getIdentifier(name: String, defType: String): Int? {
        judge()
        if (mSkinResources == null || !isExternalSkin) {
            return context?.resources?.getIdentifier(name, defType, getPackage())
        }
        return mSkinResources?.getIdentifier(name, defType, getPackage())
    }

    fun restoreDefaultTheme() {
        judge()
        SPRepository.INSTANCE.skin_path = ""
        isExternalSkin = false
        mSkinResources = null
        notifySkinUpdate()
    }


    private fun notifySkinUpdate() {
        synchronized(mListeners) {
            for (idx in 0 until mListeners.size) {
                mListeners[idx].onSkinUpdate()
            }
        }
    }
}